# coding = utf-8
import tkinter
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox

from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
                                               NavigationToolbar2Tk)

# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
from matplotlib import lines
from matplotlib.widgets import Button
from matplotlib import pyplot

import numpy as np

from scipy import spatial

import dxfgrabber

import dxf_element
from dxf_element import BaseEntityMethod

from enum import Enum, unique

from map_artist.map_line import MapLine
from map_artist.map_polygon import MapPolygon
from map_artist.map_patches import MapPatches
from map_artist.attribute_def import (APPENDIX_ATTRIBUTE_VALUE,
                                      ROAD_AREA_VALUE)
import map_artist

from map_widget.para_widget import (RefLineParaWidget, AppendixParaWidget,
                                    EmptyWidget)


def calcPointsDistance(p0, p1):
    value = 0
    for i in range(len(p0)):
        value += (p1[i] - p0[i])**2
    return (np.sqrt(value))


def calcAngle(p0, p1, p2):
    """
    计算有向线段p0-p1与p0-p2的夹角(p01向p02旋转，-pi~pi, 逆时针为正）, 不考虑z轴位移
    """
    angle01_ = np.arctan2(p1[1] - p0[1], p1[0] - p0[0])
    # if angle01_ < 0:
    #     angle01_ += 2 * np.pi
    angle02_ = np.arctan2(p2[1] - p0[1], p2[0] - p0[0])
    # if angle02_ < 0:
    #     angle02_ += 2 * np.pi
    # print('angle01={}, angle02={}'.format(angle01_, angle02_))
    angle = angle02_ - angle01_
    if angle > np.pi * 2:
        angle -= 2 * np.pi
    elif angle < 0:
        angle += 2 * np.pi
    return angle


@unique
class DataProcSchedule(Enum):
    """
    处理步骤枚举定义
    """
    START = 0
    REF_PICKED = 1
    EPS_PICKED = 2
    REF_CONFIGED = 3
    BOARD_PICKED = 4
    BOARD_COMBINED = 5
    POINTS_DONE = 6


@unique
class AppendixSchedule(Enum):
    """
    附属物处理步骤
    """
    START = 0
    GET_BELONG = 1
    CONFIG = 2
    SAVE = 3


def checklineInFile(file_dir, index):
    with open(file_dir, 'r') as file:
        lines = file.readlines()
        for line in lines:
            ref_line = dxf_element.RefLineDef()
            ref_line.fromStr(line)
            if ref_line.index == index:
                return True
        return False


class DataProcess:
    """
    基于matplotlib的数据处理方法
    """
    def __init__(self):
        self.schedule = DataProcSchedule.START
        self.dxfentity_dict = {}
        self.ref_line_dict = {}  # 保存好，处理好的refLine
        self.handle_ref_flag = True
        self.tips_enable = False
        self.initALineVarials()
        self.initAppendixVarials()

        self.appendix_dict = {}

        self.total_road_cnt = 0
        self.saved_road_cnt = 0

        self.margins = 0

        self.check_connect_flag = False

        # 直接利用右键菜单来进行处理 todo
        self.current_artist = None
        self.current_artist_belongs = list([])
        self.current_artist_boards = list([])
        self.current_artist_start = None
        self.current_artist_end = None
        self.current_artist_start_id = None
        self.current_artist_end_id = None

        self.main_frame = None

        # 通过tkinter.StringValue来传递事件
        self.ref_line_config_done_event = tkinter.StringVar()
        self.ref_line_config_done_event.trace_variable(
            "r", self.ref_config_event_handler)

        self.appendix_config_done_event = tkinter.StringVar()
        self.appendix_config_done_event.trace_variable(
            "r", self.appendix_config_event_handler)
        pass

    def saveAppendixAttribute(self):
        """
        保存附属物属性:id + belongs计数 + belongs列表 + 属性index + 顶点计数 + 顶点坐标列表
        """
        file_name = self.fold_dir + '_appendix_attribute.json'
        try:
            read_file = open(file_name, 'r')
            lines = read_file.readlines()
            indexes = self.current_appendix_patches.get_indexes()
            write_file = open(file_name, 'w+')
            for line in lines:
                index = int(line.split(',', 1)[0])
                if not index in indexes:
                    write_file.write(line)
        except FileNotFoundError:
            write_file = open(file_name, 'w+')
        else:
            for patch in self.current_appendix_patches:
                if patch.dxftype == 'POINT':  # FIXME : 目前没有找到好的同时继承line和polygon的方法,先这么判断
                    line = patch.pointToAppendixAttributeStr()
                    print('attribute value =', line)
                else:
                    line = patch.toAppendixAttributeStr()
                    print('attribute value =', line)
                write_file.write(line)
                write_file.write('\n')
            write_file.close()

    def saveAppendixBelongs(self):
        """
        保存附属物归属: ref_line_id + 附屬物個數 + 附属物id
        """
        # 需要根据appendixAttribute文件构建,因为不确定是否有belong更新,因此必须新写
        try:
            file_name = self.fold_dir + '_appendix_attribute.json'
            appendix_belongs_dict = {}
            appendix_attribute_file = open(file_name, 'r')
            appendix_attribute_lines = appendix_attribute_file.readlines()

            for line in appendix_attribute_lines:
                value = line.split(',')
                belongs_number = int(value[1])
                appendix_belongs_dict[int(value[0])] = list(
                    int(ref_id) for ref_id in value[2:2 + belongs_number]
                )  # 字典内容:附属物id : [参考线id_i, 参考线id_j, ...]
        except FileNotFoundError:
            return

        ref_appendix_dict = {}
        for key in appendix_belongs_dict.keys():
            value = appendix_belongs_dict[key]
            print('key:value = {}:{}'.format(key, value))
            for ref_id in value:
                if ref_id not in ref_appendix_dict.keys():
                    ref_appendix_dict[ref_id] = list(
                        [key])  # 字典内容: 参考线id : [附属物id_i, 附属物id_j, ...]
                else:
                    ref_appendix_dict[ref_id].append(key)
        try:
            file_name = self.fold_dir + '_appendix_belongs.json'
            write_file = open(file_name, 'w+')
            for ref_id in ref_appendix_dict.keys():
                line = list([])
                line.append('{}'.format(ref_id))
                line.append('{}'.format(len(ref_appendix_dict[ref_id])))
                for appendix_id in ref_appendix_dict[ref_id]:
                    line.append('{}'.format(appendix_id))
                line = ','.join(line)
                print('ref-appendix:', line)
                write_file.write(line)
                write_file.write('\n')
        except FileExistsError:
            print('file is occupied')
            return
        finally:
            write_file.close()
        pass

    def appendix_config_event_handler(self, *args):
        """
        appendix_config_widget 利用 read 来产生回调,和ref_config_event_handler 可以复用,只是为了简单,先分开
        """
        if self.current_artist is None:
            return
        else:
            # 更新数据
            objecttype = self.para_widget.box_area_type['value'].index(
                self.para_widget.box_area_type.get())
            self.current_appendix_patches.setObjectType(objecttype)
            # FIXME : list(int(index) for index in self.para_widget.belongs_id_list.get().split(','))
            self.current_appendix_patches.setBelongs(
                list(artist.index for artist in self.current_artist_belongs))
            if not self.current_artist.belongs:
                messagebox.showerror(message='附属物未规定所属车道')
                return
            # 保存
            self.current_appendix_patches.setConfiged()
            self.saveAppendixAttribute()
            self.saveAppendixBelongs()
            # 清空
            self.resetCurrentArtistValue()
            messagebox.showinfo(message='get call to save appendix')
            # FIXME 下一步尝试停留在当前状态
            self.updateParaWidget(True)
        self.figure.canvas.draw()

    def ref_config_event_handler(self, *args):
        """
        refline_widget触发read来产生回调,
        """
        if self.current_artist is None:
            return
        # 首先对self.current_artist中的各个值进行更新
        if self.current_artist.maptype == 'ref':
            self.current_artist.speed_limited[0] = float(
                self.para_widget.speed_lower.get())
            self.current_artist.speed_limited[1] = float(
                self.para_widget.speed_upper.get())
            self.current_artist.height_limited = float(
                self.para_widget.height_limited.get())
            self.current_artist.lanes_number = int(
                self.para_widget.lanes_number.get())
            self.current_artist.ratio = float(self.para_widget.ratio.get())
            self.current_artist.direction = self.para_widget.getDirection()
            self.current_artist.road_material = self.para_widget.material_box[
                'value'].index(self.para_widget.material_box.get())
            self.current_artist.area = self.para_widget.box_area_type[
                'value'].index(self.para_widget.box_area_type.get())

        # 判断数据是否完备
        if self.current_artist.maptype == 'ref' and self.current_artist_boards and self.current_artist.marked_points:  # 所有数据都具备
            boards_dict = self.checkBoardLines()  # 拼接边界

            if len(boards_dict) != self.current_artist.lanes_number + 1:
                messagebox.showerror(title='Error',
                                     message='请确认车道边界线数量{}与车道数量{}是否匹配'.format(
                                         len(boards_dict),
                                         self.current_artist.lanes_number)
                                     )  # FIXME: 下一版本允许自行选择,从而解决车道分割物是较大物体的bug
            else:
                # 拼接车道线,并排序
                boards_xydata = list([])
                for index in boards_dict.keys():
                    board_xydata = list([])
                    for board in boards_dict[index]:
                        # 把每条边界中多条线的数据拼接成一条
                        board_xydata.extend(board.get_xydata())
                    boards_xydata.append(board_xydata)
                self.current_artist.sortBoards(boards_xydata)
                # 计算车道宽度
                self.current_artist.calcWidth()
                # 保存数据
                file_name = self.fold_dir + '_ref_line.json'
                self.saveCurrentRefLine(file_name)
                file_name = self.fold_dir + '_ref_points.json'
                self.saveCurrentRefPoints(file_name)
                messagebox.showinfo(title='Success', message='地图参考线信息已经保存')
                # 设置当前对象为已经配置
                self.current_artist.setConfiged()
                # 保存成dict,方便后续connect_map处理,为了简单,双向车道保存两次,一次为index, 一次为 -index,但artist的值不变
                self.ref_line_dict[
                    self.current_artist.get_index()] = self.current_artist
                if self.current_artist.direction & (1 << 5):
                    self.ref_line_dict[
                        -1 *
                        self.current_artist.get_index()] = self.current_artist
                # 重新初始化地图操作对象: 对于参考线必须重置,否则无法选择下一条参考线,或者根据configed来: FIXME 先重置,之后根据configed来进行
                self.resetCurrentArtistValue()
                # FIXME
                self.updateParaWidget(True)
        else:
            if not self.current_artist.marked_points:
                message = '未选择参考线起点'
            else:
                message = '未选择车道边界'
            messagebox.showerror(title='Error', message=message)
        self.figure.canvas.draw()

    def saveCurrentRefLine(self, file_name):
        try:
            read_file = open(file_name, 'r')
            lines = read_file.readlines()
            write_file = open(file_name, 'w+')
            for line in lines:
                line_id = abs(int(line.split(
                    ',', 1)[0]))  # FIXME:目前先用id替代,之后用artist.equal
                if self.current_artist.get_index() != line_id:
                    write_file.write(line)
        except FileNotFoundError:
            write_file = open(file_name, 'w+')
        finally:
            write_file.write(self.current_artist.toRefLineStr())
            write_file.write('\n')
            # 双向
            if self.current_artist.direction & (1 << 5):
                print('index = -', self.current_artist.get_index())
                write_file.write(self.current_artist.toNegRefLineStr())
                write_file.write('\n')
            write_file.close()

    def saveCurrentRefPoints(self, file_name):
        try:
            read_file = open(file_name, 'r')
            lines = read_file.readlines()
            write_file = open(file_name, 'w+')
            for line in lines:
                line_id = abs(int(line.split(
                    ',', 1)[0]))  # FIXME: 目前用id替代,之后用artist.equal
                if self.current_artist.get_index() != line_id:
                    write_file.write(line)
        except FileNotFoundError:
            write_file = open(file_name, 'w+')
        finally:
            # FIXME
            xy_data = self.current_artist.get_xydata()
            # demo
            print('size: ', len(self.current_artist.cuv_list_),
                  len(self.current_artist.gcuv_list_),
                  len(self.current_artist.slength_list_),
                  len(self.current_artist.heading))
            for i, _ in enumerate(xy_data):
                write_file.write(self.current_artist.toRefPointStr(i))
                write_file.write('\n')
            if self.current_artist.direction & (1 << 5):
                for i, _ in enumerate(xy_data):
                    write_file.write(self.current_artist.toNegRefPointStr(i))
                    write_file.write('\n')
            write_file.close()

    def focusOnArtist(self, artist):
        """
        聚焦显示
        """
        self.figure.canvas.draw()

    def setParaFrameWindow(self, para_frame_window):
        """
        为self.para_display_frame赋值
        """
        self.para_display_frame = para_frame_window

    def initParaDisplayFrame(self):
        """
        初始化一个空frame
        """
        self.para_widget = EmptyWidget(self.para_display_frame)

    def updateParaWidget(self, new_tag):
        """
        重绘 para_frame 数据
        """
        print('called , new is ', new_tag)
        if new_tag:
            self.para_widget.destroy()
            if self.current_artist is None:
                self.para_widget = EmptyWidget(self.para_display_frame)
            elif self.current_artist.maptype == 'appendix':  # 因为appendix允许同时选择多个地图元素,因此即使选中了configed的元素,也不显示已经配置好的信息
                self.para_widget = AppendixParaWidget(
                    self.para_display_frame, self.appendix_config_done_event)
                self.para_widget.id_value.set('{}'.format(
                    self.current_appendix_patches.get_indexes()))
                # 得到中心
                center_list = self.current_appendix_patches.getCenter()
                self.para_widget.updateAttributeFrame(
                    self.current_appendix_patches.get_indexes(), center_list)
            elif self.current_artist.maptype == 'ref':
                self.para_widget = RefLineParaWidget(
                    self.para_display_frame, self.ref_line_config_done_event)
                # 根据当前选中的ref的数据更新显示
                self.para_widget.current_line_id.set('{}'.format(
                    self.current_artist.get_index()))
                self.para_widget.speed_lower.set(
                    self.current_artist.speed_limited[0])
                self.para_widget.speed_upper.set(
                    self.current_artist.speed_limited[1])
                self.para_widget.height_limited.set(
                    self.current_artist.height_limited)
                self.para_widget.lanes_number.set(
                    self.current_artist.lanes_number)
                self.para_widget.curvature.set('{:.2f}'.format(
                    self.current_artist.curvature))
                self.para_widget.ratio.set('{:.2f}'.format(
                    self.current_artist.ratio))
                self.para_widget.setDirection(self.current_artist.direction)
                self.para_widget.material_box.current(
                    self.current_artist.road_material)
                self.para_widget.box_area_type.current(
                    self.current_artist.area)
        else:
            if self.current_artist is not None and self.current_artist.maptype == 'appendix':
                self.para_widget.id_value.set('{}'.format(
                    self.current_appendix_patches.get_indexes()))
                # 得到中心
                center_list = self.current_appendix_patches.getCenter()
                self.para_widget.updateAttributeFrame(
                    self.current_appendix_patches.get_indexes(), center_list)
                pass

    def resetCurrentArtistValue(self):
        """
        重置当前对象的操作值列表
        """
        while self.current_artist_belongs:
            belong = self.current_artist_belongs.pop(0)
            belong.setDeselected()
        while self.current_artist_boards:
            board = self.current_artist_boards.pop(0)
            board.setDeselected()

        ax = self.figure.gca()
        if self.current_artist is not None:
            if self.current_artist.maptype == 'ref':
                for _, index in enumerate(self.current_artist.marked_points):
                    ax.lines.remove(self.current_artist.marked_points[index])
                self.current_artist.marked_points = dict([])
            self.current_artist.setDeselected()
            self.current_artist = None
        while self.current_appendix_patches:
            self.current_appendix_patches.pop(0)

    def initAppendixVarials(self):
        """
        初始化 附属物 变量
        """
        self.current_appendix_id = 0
        self.current_appendix_to_id = 0
        self.current_appendix_line_list = []
        self.appendix_schedule = AppendixSchedule.START  # 'PICK_BELONGS'/'CONFIG'
        # self.appendix_id = 0

    def resetBoardLinesDisplay(self):
        """
        设置
        """
        for line in self.total_board_lines_list:
            line.set_picker(3)
            line.set_color(line.true_color)

    def setRefLinePicker(self, radius):
        """
        设置全部refline的picker属性
        """
        ax = self.figure.gca()
        # for map_line in ax.lines:
        #     if map_line.maptype == 'ref':
        #         map_line.set_picker(radius)
        pass

    def setBoardLinePicker(self, radius):
        """
        设置全部boardline的picker属性
        """
        # ax = self.figure.gca()
        # for line in ax.lines:
        #     if line.maptype == 'board':
        #         line.set_picker(radius)
        pass

    def setAppendixLinePicker(self, radius):
        """
        设置全部appendixline的picker属性
        """
        ax = self.figure.gca()
        # for line in ax.lines:
        #     if line.maptype == 'appendix':
        #         line.set_picker(radius)
        # for patch in ax.patches:
        #     if patch.maptype == 'appendix':
        #         patch.set_picker(radius)
        pass

    def removeLineEPs(self, figure):
        """
        关闭线段端点的显示
        """
        ax = figure.gca()
        ax.lines.remove(self.current_artist_start_id)  # 将端点直接从ax的line2d中移除
        ax.lines.remove(self.current_artist_end_id)
        figure.canvas.draw()

    def resetRefLineDisplay(self, color):
        self.current_line.set_color(color)

    def toggleTipsEnable(self):
        """
        提示信息使能
        """
        self.tips_enable = not self.tips_enable

    def savePhysicalPoints(self, entity, true_color, id, file):

        physical_line = MapLine()
        physical_line.fromDXFEntity(entity, true_color)
        xydata = physical_line.get_xydata()
        for xy in xydata:
            data = '{},{},{}'.format(id, xy[0], xy[1])
            file.write(data)
            file.write('\n')
        pass

    def savePhysicalCircle(self, entity, id, file):
        """
        在file中写入circle数据
        id
        圆心x
        圆心y
        半径
        起始角度[0~2pi),x轴为起点,逆时针旋转
        结束角度[0,2pi)
        * 可能出现结束角度比起始角度小的情况
        """
        data = '{},{},{},{},{},{}'.format(id, entity.center[0],
                                          entity.center[1], entity.radius,
                                          entity.start_angle * np.pi / 180,
                                          entity.end_angle * np.pi / 180)
        # print(data)
        file.write(data)
        file.write('\n')

    def savePhysicalLine(self, entity, id, file):
        """
        在file中写入line数据
        id
        起点x
        起点y
        终点x
        终点y
        """
        data = '{},{},{},{},{}'.format(id, entity.start[0], entity.start[1],
                                       entity.end[0], entity.end[1])
        # print(data)
        file.write(data)
        file.write('\n')

    def loadRefLineData(self):
        """
        将已经存在的ref_line数据载入到self.ref_line_dict中
        """
        self.configed_ref_dict = {}
        try:
            ref_line_file_dir = self.fold_dir + '_ref_line.json'
            ref_line_file = open(ref_line_file_dir, 'r')
            lines = ref_line_file.readlines()
            self.saved_road_cnt = 0
            self.saved_road_length = 0
            self.bidirection_road_cnt = 0
            # 对于-index,其所有值应该与index保持一致,以和后续处理保持一致(即不会产生新的MapLine数据)
            for line in lines:
                index = int(line.split(',')[0])
                if index > 0:
                    self.saved_road_cnt += 1
                    self.configed_ref_dict[index] = line
                else:
                    self.bidirection_road_cnt += 1
            ref_line_file.close()
            messagebox.showinfo(title='完成', message='参考线数据导入成功')
            pass
        except FileNotFoundError:
            messagebox.showinfo(title='完成', message='无参考线信息')
            return
        finally:
            pass
        pass

    def loadAppendixData(self):
        """
        从appendix_atrribute.json中载入数据到self.configed_appendix_ids.append
        """
        self.configed_appendix_dict = {}  # 用id来记录哪些appendix已经被配置过了
        try:
            file_name = self.fold_dir + '_appendix_attribute.json'
            file = open(file_name, 'r')
            lines = file.readlines()
            for line in lines:
                self.configed_appendix_dict[int(line.split(',')[0])] = line
            file.close()
        except FileNotFoundError:
            return
        finally:
            pass

    def loadConnectDict(self):
        self.connect_dict = {}
        file_name = self.fold_dir + '_connect_map.json'
        try:
            file = open(file_name, 'r')
            lines = file.readlines()
            for line in lines:
                str_list = line.split(',')
                if len(str_list) > 1:
                    self.connect_dict[int(str_list[0])] = list(
                        int(value) for value in str_list[1:])
                else:
                    self.connect_dict[int(str_list[0])] = []
        except FileNotFoundError:
            file = open(file_name, 'w+')
            pass
        finally:
            file.close()
            pass

    def loadDXFLayer(self, file_name):
        layers_dict = {}
        file = dxfgrabber.readfile(file_name)
        for entity in file.entities:
            if entity.layer != '0':
                layers_dict[entity.layer] = 1
        return list(layers_dict.keys())

    def loadData(self,
                 dir,
                 figure,
                 ref_layer=['ref_layer'],
                 board_layer=['board_layer'],
                 appendix_layer=['appendix_layer']):
        """
        从dxf文件中读入数据
        """
        self.para_widget.destroy()
        self.figure = figure
        self.current_appendix_patches = MapPatches()
        self.fold_dir = dir
        # read exist file
        self.loadRefLineData()
        self.loadAppendixData()
        self.loadConnectDict()
        self.initParaDisplayFrame()

        file_dir = str(dir) + ('.dxf')
        self.scatter_step = 0.5
        self.ref_line2D_dict = {}

        ax = self.figure.gca()

        try:
            """
            保存边界 physical
            """
            file = dxfgrabber.readfile(file_dir)  # open dxf file
        except FileNotFoundError:
            messagebox.showinfo('File Not Exist!')
        else:
            try:
                circle_file_dir = self.fold_dir + '_physical_circle.json'
                circle_file = open(circle_file_dir,
                                   'w+')  # open file in 'w+' mode
                circle_id = 0

                line_file_dir = self.fold_dir + '_physical_line.json'
                line_file = open(line_file_dir, 'w+')
                line_id = 0

                physical_file_dir = self.fold_dir + '_physical_points.json'
                physical_file = open(physical_file_dir, 'w+')
                physical_id = 0
            except FileExistsError:
                messagebox.showerror('Physical Files Do Not Exist!')
            else:
                for entity in file.entities:
                    if entity.layer == 'physical':  # 强制要求cad图上必须有physical这个图层
                        true_color = map_artist.getEntityTrueColor(
                            file.layers, entity)
                        # print('Now save physical:', entity.dxftype)
                        physical_id += 1
                        self.savePhysicalPoints(entity, true_color,
                                                physical_id, physical_file)
                        if entity.dxftype == 'ARC':
                            circle_id += 1
                            self.savePhysicalCircle(entity, circle_id,
                                                    circle_file)
                        elif entity.dxftype == 'LINE':
                            line_id += 1
                            self.savePhysicalLine(entity, line_id, line_file)
                        pass
                circle_file.close()
                line_file.close()
                physical_file.close()

        try:
            """
            显示地图元素
            """
            file = dxfgrabber.readfile(file_dir)
            road_id = 0
            appendix_id = 0
            board_id = 0
            for entity in file.entities:
                true_color = map_artist.getEntityTrueColor(file.layers, entity)
                if entity.layer in ref_layer:
                    road_id += 1
                    line0 = MapLine()
                    line0.fromDXFEntity(entity, true_color)
                    line0.set_index(road_id)
                    if line0.get_index() in self.configed_ref_dict.keys():
                        line0.setConfiged()
                        start, _ = line0.fromRefLineStr(
                            self.configed_ref_dict[line0.get_index()]
                        )  # 因为涉及到ep change,因此如果没有完全载入所有数据的情况下,不能根据存储更新数据 !!!!
                        l_ss = calcPointsDistance(line0.start, start)
                        if l_ss > 1.0:
                            line0.changeEndPoints()
                        self.ref_line_dict[line0.get_index()] = line0
                    ax.add_line(line0)
                    line0.maptype = 'ref'
                    line0.set_picker(3)
                    self.dxfentity_dict[road_id] = entity
                    self.ref_line2D_dict[road_id] = line0
                elif entity.layer in board_layer:
                    board_id += 1
                    line0 = MapLine()
                    line0.fromDXFEntity(entity, true_color)
                    ax.add_line(line0)
                    line0.maptype = 'board'
                    line0.set_picker(3)
                    line0.set_index(board_id)
                    """
                    add code here, 根据颜色来确定黄实线
                    """
                elif entity.layer in appendix_layer:
                    appendix_id += 1
                    if entity.dxftype == 'POINT':
                        point0 = MapLine()
                        point0.fromDXFEntity(entity, true_color)
                        point0.set_marker('o')
                        point0.set_index(appendix_id)
                        point0.maptype = 'appendix'
                        if point0.get_index(
                        ) in self.configed_appendix_dict.keys():
                            point0.setConfiged()
                            # point0.pointFromAppendixAttributeStr(self.configed_appendix_dict[point0.get_index()])
                        point0.set_picker(3)
                        ax.add_line(point0)
                    else:
                        polygon0 = MapPolygon()
                        polygon0.fromDXFEntity(entity, true_color)
                        polygon0.set_index(appendix_id)
                        polygon0.maptype = 'appendix'
                        if polygon0.get_index(
                        ) in self.configed_appendix_dict.keys():
                            polygon0.setConfiged()
                            # polygon0.fromAppendixAttributeStr(self.configed_appendix_dict[polygon0.get_index()])
                        ax.add_patch(polygon0)
                        polygon0.set_picker(3)
        except FileNotFoundError:
            messagebox.showerror(title='错误', message='文件不存在或被占用')
        finally:
            if self.tips_enable:
                messagebox.showinfo(title='第一步', message='请选择一条道路参考线')
            self.total_road_cnt = road_id
            """
            add code here: 保存边界数据
            """
            return

    def addNewLine(self, line):
        return

    def setAX(self, ax):
        """
        设定ax的范围,方便后续缩放
        """
        ax.margins(0.05)
        self.xmin, self.xmax = ax.get_xlim()
        self.ymin, self.ymax = ax.get_ylim()
        self.xscale = (self.xmax - self.xmin) / 10
        self.yscale = (self.ymax - self.ymin) / 10

    def addButton(self):
        axprev = self.figure.add_axes([0.2, 0.02, 0.1,
                                       0.04])  # [self.xmax-1, 0.05, 10, 10]
        axnext = self.figure.add_axes([0.4, 0.02, 0.1, 0.04
                                       ])  # [self.xmax-0.9, 0.05, 0.1, 0.075]
        bnext = Button(axnext, 'Next')
        bprev = Button(axprev, 'Previous')
        return

    def onButtonClick(self, event):
        pass

    def getLine(self, picked_line):
        """
        获取被pick线段的line2d入口和id
        """
        self.current_line = picked_line
        self.current_line_id = picked_line.get_index()
        pass

    def getEndPoints(self, artist, m_point):
        """
        根据m_point，得到被pick线段的两个端点中离m_point较近的那个点
        """
        line_data = artist.get_xydata()
        # self.ref_lines[-1].set_color('k')
        l0 = calcPointsDistance(m_point, line_data[0])
        l1 = calcPointsDistance(m_point, line_data[-1])
        if l0 > l1:
            artist.changeEndPoints()
        line_data = artist.get_xydata()
        return line_data[0], line_data[-1]

    def sortBoards(self):
        """
        对board进行重排序，保证按照由左到右（按照行车方向）排列
        1.计算board中与start最近点
        2.计算该最近点与行车方向的夹角，当为正时，在左侧；反之在右侧
        """
        dist_list_ = []
        data_list_ = []
        for tree in self.current_boards_tree_list:  # 获取各条道路边界上的最近点
            dist_, index_ = tree.query(self.current_line_startpoint, 1)
            dist_list_.append(dist_)
            data_list_.append(tree.data[index_])
        # 计算角度
        xy_data_ = self.current_line.get_xydata()
        p0_ = xy_data_[0]
        p1_ = xy_data_[1]
        angle_list_ = []
        angle_index_over0_ = []
        angle_index_below0_ = []
        for i_, p2_ in enumerate(data_list_):  # 计算道路边界与车道线的夹角
            angle_ = calcAngle(p0_, p1_, p2_)
            angle_list_.append(angle_)
            print(angle_)
            if angle_ > 0:
                angle_index_over0_.append(i_)
            else:
                angle_index_below0_.append(i_)

        if not angle_index_over0_ or not angle_index_below0_:
            print('over list:{}'.format(angle_index_over0_))
            print('below list:{}'.format(angle_index_below0_))
            return False
        else:
            # 在angle>0值与<0值中，分别对dist_list_进行排序
            dist_over0_ = list(dist_list_[i] for i in angle_index_over0_)
            dist_over0_.sort(reverse=True)
            self.current_board_sign_list = list(-1 for _ in dist_over0_)
            dist_below0_ = list(dist_list_[i] for i in angle_index_below0_)
            dist_below0_.sort(reverse=True)
            self.current_board_sign_list.extend(list(1 for _ in dist_below0_))
            dist_over0_.extend(dist_below0_)  # 把距离排序拼接
            # 获取距离的原序号，并对boards_list 和 tree_list进行重排
            orig_index_ = list(
                dist_over0_.index(value) for value in dist_list_)

            self.current_boards_list = list(self.current_boards_list[i]
                                            for i in orig_index_)
            self.current_boards_tree_list = list(
                self.current_boards_tree_list[i] for i in orig_index_)
            # self.current_boards_tree_list[dist_index_over0_]
            return True

    def onWheel(self, event):
        """
        以鼠标位置为中心进行缩放
        """
        direction = event.button
        x_center = event.xdata
        y_center = event.ydata
        ax = event.inaxes
        x_min, x_max = ax.get_xlim()
        x_len = x_max - x_min
        y_min, y_max = ax.get_ylim()
        y_len = y_max - y_min
        if direction == "down":
            x_l = max(x_center - x_len, self.xmin)
            x_r = min(x_center + x_len, self.xmax)
            x_lim = (min(x_l, x_r), max(x_l, x_r))
            y_l = max(y_center - y_len, self.ymin)
            y_r = min(y_center + y_len, self.ymax)
            y_lim = (min(y_l, y_r), max(y_l, y_r))
        elif direction == 'up':
            x_l = max(x_center - x_len / 4, self.xmin)
            x_r = min(x_center + x_len / 4, self.xmax)
            y_l = max(y_center - y_len / 4, self.ymin)
            y_r = min(y_center + y_len / 4, self.ymax)
            x_lim = (min(x_l, x_r), max(x_l, x_r))
            y_lim = (min(y_l, y_r), max(y_l, y_r))
        ax.set_xlim(x_lim)
        ax.set_ylim(y_lim)
        self.figure.canvas.draw()
        return

    def saveConnectMap(self, file_name, connect_dict):
        with open(file_name, 'w+') as w:
            for key in connect_dict.keys():
                w.write(str(key))
                for value in connect_dict[key]:
                    w.write(',')
                    w.write(str(value))
                w.write('\n')
        pass

    def makeConnectMap(self):
        """
        理论上所有数据已经存在self.ref_line_dict中
        所以每次都是直接重写
        """
        file_name = self.fold_dir + '_connect_map.json'
        start_point_list = []
        end_point_list = []
        key_list = []
        # 根据ref_line_dict，得到connect_dict
        for key in self.ref_line_dict.keys():
            ref_line = self.ref_line_dict[key]
            if key > 0:
                start_point_list.append(ref_line.start)
                end_point_list.append(ref_line.end)
            else:
                start_point_list.append(ref_line.end)
                end_point_list.append(ref_line.start)
            key_list.append(key)
            self.connect_dict[key] = list([])
        print('end=', end_point_list)
        print('start=', start_point_list)

        for i, point0 in enumerate(end_point_list):
            for j, point1 in enumerate(start_point_list):
                """
                当终点和起点之间的距离小于0.5m(理论可以更小)，则认为是相接的两条车道。需要排除双向车道 : 这种方法基于车道是完全首尾相连的
                """
                if calcPointsDistance(point0, point1) < 0.5 and abs(
                        key_list[i]) != abs(key_list[j]):
                    self.connect_dict[key_list[i]].append(key_list[j])
        self.saveConnectMap(file_name, self.connect_dict)
        return

    def getRefLinePoints(self):
        """
        直接取ref_line上各点与board_line的最近点作为距离
        """
        # if self.sortBoards():  # 对boardline进行排序，按照行车方向，以左侧为正，由远到近
        #     dxf_entity = self.dxfentity_dict[self.current_line_id]
        #     self.ref_line_points = []  # 建立一个列表，存储参考线点数据
        #     xy_data = self.current_line.get_xydata()  # 获取当前gps坐标
        #     slength = 0
        #     theta = 0
        #     # self.slength_ = 0
        #     if dxf_entity.dxftype == 'LINE':
        #         for i, xy in enumerate(xy_data):
        #             ref_point = dxf_element.RefPointDef()  # 单个点
        #             ref_point.gps_ = dxf_element.GPSPointDef(xy[0], xy[1])
        #             ref_point.lanes_ = self.lanes_number
        #             dist_list = []
        #             for i, tree in enumerate(
        #                     self.current_boards_tree_list):  # 获取车道宽度
        #                 dist, _ = tree.query(xy, 1)
        #                 dist_list.append(dist)
        #             ref_point.width_list_ = list([dist_list[i]*self.current_board_sign_list[i], \
        #                                           dist_list[i+1]*self.current_board_sign_list[i+1]] \
        #                                           for i in range(len(dist_list)-1))
        #             ref_point.cuv_ = 0
        #             ref_point.gcuv_ = 0
        #             ref_point.theta_ = calcAngle(self.current_line_startpoint, \
        #                                     self.current_line_endpoint, \
        #                                    [self.current_line_startpoint[0], self.current_line_startpoint[1]+1]) # 因为是直线，可以用终点与起点形成的线段与y轴的夹角
        #             if ref_point.theta_ < 0:
        #                 ref_point.theta_ += np.pi * 2
        #             elif ref_point.theta_ > 2 * np.pi:
        #                 ref_point.theta_ -= 2 * np.pi
        #             slength = calcPointsDistance(xy,
        #                                          self.current_line_startpoint)
        #             ref_point.slength_ = slength  # 实际就是step
        #             self.ref_line_points.extend([ref_point])
        #     elif dxf_entity.dxftype == 'ARC':
        #         # add
        #         if dxf_entity.start_angle > dxf_entity.end_angle:
        #             start_angle_ = dxf_entity.start_angle * np.pi / 180 - 2 * np.pi
        #         else:
        #             start_angle_ = dxf_entity.start_angle * np.pi / 180
        #         # start_angle_ -= np.pi/2
        #         end_angle_ = dxf_entity.end_angle * np.pi / 180
        #         # end_angle_ -= np.pi/2
        #         angle_step_ = self.scatter_step / dxf_entity.radius
        #         angle_list_ = np.arange(start_angle_, end_angle_, angle_step_)
        #         angle_list_ = np.append(angle_list_, end_angle_)
        #         if self.current_ep_changed:  # 逆序
        #             angle_list_ = angle_list_[::-1] + np.pi
        #         # angle_list_ = 2*np.pi - angle_list_ # 因为整个要改为以顺时针为正
        #         for i, xy in enumerate(xy_data):
        #             ref_point = dxf_element.RefPointDef()  # 单个点
        #             ref_point.gps_ = dxf_element.GPSPointDef(xy[0], xy[1])
        #             ref_point.lanes_ = self.lanes_number
        #             dist_list = []
        #             for j, tree in enumerate(
        #                     self.current_boards_tree_list):  # 获取车道宽度
        #                 dist, _ = tree.query(xy, 1)
        #                 dist_list.append(dist)
        #             ref_point.width_list_ = list([
        #                 dist_list[k] *
        #                 self.current_board_sign_list[k], dist_list[k + 1] *
        #                 self.current_board_sign_list[k + 1]
        #             ] for k in range(len(dist_list) - 1))
        #             ref_point.cuv_ = 1 / dxf_entity.radius
        #             ref_point.gcuv_ = 0
        #             print('current i=', i)
        #             ref_point.theta_ = 2 * np.pi - angle_list_[i]  # 修改为以逆时针为负
        #             if ref_point.theta_ < 0:
        #                 ref_point.theta_ += 2 * np.pi
        #             elif ref_point.theta_ > 2 * np.pi:
        #                 ref_point.theta_ -= 2 * np.pi
        #             slength = dxf_entity.radius * abs(angle_list_[i] -
        #                                               angle_list_[0])
        #             # print('Arc Slength=', slength)
        #             ref_point.slength_ = slength  # 实际就是step
        #             self.ref_line_points.extend([ref_point])
        #     return True
        # else:
        #     return False
        pass

    def deleteALine(self):
        pass

    def appendixHandler(self, event):
        # """
        # 附属物主处理函数
        # """
        # # todo 暂时只处理附属物,和其他处于并行阶段
        # artist = event.artist
        # if artist.maptype == 'appendix':
        #     self.current_appendix_patches.toggleElement(artist)
        pass

    def resetAppendixLineListColor(self):
        for line in self.current_appendix_line_list:
            print('run here')
            if line.maptype == 'appendix':
                line.set_color(line.true_color)
            else:
                line.set_color(line.true_color)
        return

    def onMapArtistPickHandle(self, event):
        """
        判断点选的artist的maptype类型
        如果current_artist为空,
            maptype为appendix
                标记选中状态,同时关闭appendix picker,开始ref picker
        如果current_artist为appendix,点选的为r
        """
        artist = event.artist
        ax = self.figure.gca()

        new_para_tag = False
        # 左键点击
        if event.mouseevent.button == 1:
            # 选中了一个地图元素,新建相应窗口
            if self.current_artist is None:  # 新建
                new_para_tag = True
                if artist.maptype == 'ref' or artist.maptype == 'appendix':
                    self.current_artist = artist
                    self.current_artist.setSelected()
                    if self.current_artist.maptype == 'appendix':
                        self.current_appendix_patches.append(
                            self.current_artist)
                    ax.set_xlabel(
                        'A {} is selected, Right Click on it to deselect.'.
                        format(self.current_artist.maptype))
            # 处理要素是ref
            elif self.current_artist.maptype == 'ref':
                if self.current_artist == artist:
                    m_point = [event.mouseevent.xdata, event.mouseevent.ydata]
                    startpoint, endpoint = self.getEndPoints(
                        self.current_artist, m_point)
                    # 为方便观察，将起点和终点显示出来
                    ax = self.figure.gca()
                    if self.current_artist.addMarkedPoint(0, 'green', 'o'):
                        ax.add_line(artist.marked_points[0])
                    if self.current_artist.addMarkedPoint(-1, 'red', 'o'):
                        ax.add_line(artist.marked_points[-1])
                    self.para_widget.updateEndpointsValue(startpoint, endpoint)
                elif self.current_artist != artist and artist.maptype == 'board':
                    if not artist in self.current_artist_boards:
                        self.current_artist_boards.append(artist)
                        boards = list(board.get_index()
                                      for board in self.current_artist_boards)
                        self.para_widget.updateBoardsValue(boards)
                    artist.setSelected()
            # 当前为appendix
            elif self.current_artist.maptype == 'appendix':
                if artist.maptype == 'ref':  # 已经选中appendix,当前为ref
                    if not artist in self.current_artist_belongs:
                        self.current_artist_belongs.append(artist)
                        artist.setSelected()
                        self.para_widget.belongs_id_list.set('{}'.format(
                            list(artist.get_index()
                                 for artist in self.current_artist_belongs)))
                elif artist.maptype == 'appendix':  # 同时操作多个appendix
                    artist.setSelected()
                    self.current_appendix_patches.append(artist)  # 左键选中地图元素
        elif event.mouseevent.button == 3:  # 取消
            if self.current_artist is None:
                pass
            # 被取消元素与被处理元素一致,则认为取消本次操作
            elif self.current_artist == artist and self.current_artist.maptype == 'ref':
                self.current_artist.setDeselected()
                self.resetCurrentArtistValue()
                ax.set_xlabel('Left Click to Select An Element.')
                new_para_tag = True
            # 取消appendix列表
            elif artist in self.current_appendix_patches:
                artist.setDeselected()
                self.current_appendix_patches.remove(artist)
                if self.current_appendix_patches:
                    self.current_artist = self.current_appendix_patches[-1]
                    self.para_widget.id_value.set('{}'.format(
                        self.current_appendix_patches.get_indexes()))
                else:
                    self.current_artist = None
                    self.resetCurrentArtistValue()
                    ax.set_xlabel('Left Click to Select An Element.')
                    new_para_tag = True
            # 被取消元素在当前车道边界列表
            elif artist.maptype == 'board' and artist in self.current_artist_boards:  # remove board
                self.current_artist_boards.remove(artist)
                boards_url = list(board.get_index()
                                  for board in self.current_artist_boards)
                self.para_widget.updateBoardsValue(boards_url)
                artist.setDeselected()
            # 被取消元素在附属物的归属列表中
            elif self.current_artist.maptype == 'appendix' and artist.maptype == 'ref' and artist in self.current_artist_belongs:  # remove belong
                artist.setDeselected()
                self.current_artist_belongs.remove(artist)
                self.para_widget.belongs_id_list.set('{}'.format(
                    list(artist.get_index()
                         for artist in self.current_artist_belongs)))
        self.updateParaWidget(new_para_tag)
        self.figure.canvas.draw()

    def checkBoardLines(self):
        """
        检查boardlines是否是有效的: 会清除当前所有已经选择的边界
        依据：是否首尾相连
        ep:index
        """
        # 首先获取所有端点
        boards_cnt = 0
        boards_dict = {}
        while self.current_artist_boards:
            board = self.current_artist_boards.pop(0)
            board.setDeselected()
            boards = list([board])
            boards_cnt += 1
            xy_data = board.get_xydata()
            start = xy_data[0]
            end = xy_data[-1]
            print('now we get {} boards, begin board is {}'.format(
                boards_cnt, board.get_index()))
            for next_board in self.current_artist_boards:
                next_xy_data = next_board.get_xydata()
                next_start = next_xy_data[0]
                next_end = next_xy_data[-1]
                l_ss = map_artist.calcPointsDistance(start, next_start)
                l_se = map_artist.calcPointsDistance(start, next_end)
                l_es = map_artist.calcPointsDistance(end, next_start)
                l_ee = map_artist.calcPointsDistance(end, next_end)
                if l_ss < 1.5 or l_se < 1.5 or l_es < 1.5 or l_ee < 1.5:
                    next_board.setDeselected()
                    boards.append(next_board)
                    # self.current_artist_boards.remove(next_board)
                    print('yes, there is a connecting board, url is {}'.format(
                        next_board.get_index()))
                    if l_ss < 1.5:
                        start = next_end
                    elif l_se < 1.5:
                        start = next_start
                    elif l_es < 1.5:
                        end = next_end
                    else:
                        end = next_start
            for board in boards:  # 为防止枚举出错,最后统一remove
                if board in self.current_artist_boards:
                    self.current_artist_boards.remove(board)
            boards_dict[boards_cnt] = boards
        return boards_dict

    def initALineVarials(self):
        """
        初始化 一次 ref_line 处理所需要的变量
        """
        self.schedule = DataProcSchedule.START
        self.speed_lower = None
        self.speed_upper = None
        self.lanes_number = 0
        self.traffic_direct = None
        self.road_material = None
        self.ratio = 0

        self.current_line = None  # line2D
        self.current_line_id = None  # line2D_url
        self.current_line_startpoint = None  # start_point
        self.current_line_endpoint = None  # end_point
        self.current_boards_number = 0  # boards_number = lanes_number + 1
        self.current_boards_list = []  # 完整车道线（每条车道线由多条Board_line组成）列表
        self.current_boards_tree_list = []  # 完整车道线KDtree列表
        self.current_board_lines_list = []  # 单条完整车道线由多条board_line组成
        self.total_board_lines_list = []
        self.current_board_line_cnt = 0  # 单挑完整车道线的board_line数量

    def callbackConnect(self, canvas):
        # canvas.mpl_connect('pick_event', self.onPick)
        canvas.mpl_connect('pick_event', self.onMapArtistPickHandle)
        canvas.mpl_connect('scroll_event', self.onWheel)
